/*
 * Copyright (C) 2008 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.collect.testing;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.collect.testing.features.CollectionSize;
import com.google.common.collect.testing.features.Feature;
import com.google.common.collect.testing.features.FeatureUtil;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import junit.framework.TestSuite;

/**
 * This builder creates a composite test suite, containing a separate test suite for each {@link
 * CollectionSize} present in the features specified by {@link #withFeatures(Feature...)}.
 *
 * @param <B> The concrete type of this builder (the 'self-type'). All the Builder methods of this
 *            class (such as {@link #named(String)}) return this type, so that Builder methods of more
 *            derived classes can be chained onto them without casting.
 * @param <G> The type of the generator to be passed to testers in the generated test suite. An
 *            instance of G should somehow provide an instance of the class under test, plus any other
 *            information required to parameterize the test.
 * @author George van den Driessche
 * @see FeatureSpecificTestSuiteBuilder
 */
@GwtIncompatible
public abstract class PerCollectionSizeTestSuiteBuilder<
        B extends PerCollectionSizeTestSuiteBuilder<B, G, T, E>,
        G extends TestContainerGenerator<T, E>,
        T,
        E>
        extends FeatureSpecificTestSuiteBuilder<B, G>
{
    private static final Logger logger =
            Logger.getLogger(PerCollectionSizeTestSuiteBuilder.class.getName());

    /**
     * Creates a runnable JUnit test suite based on the criteria already given.
     */
    @Override
    public TestSuite createTestSuite()
    {
        checkCanCreate();

        String name = getName();
        // Copy this set, so we can modify it.
        Set<Feature<?>> features = Helpers.copyToSet(getFeatures());
        List<Class<? extends AbstractTester>> testers = getTesters();

        logger.fine(" Testing: " + name);

        // Split out all the specified sizes.
        Set<Feature<?>> sizesToTest = Helpers.<Feature<?>>copyToSet(CollectionSize.values());
        sizesToTest.retainAll(features);
        features.removeAll(sizesToTest);

        FeatureUtil.addImpliedFeatures(sizesToTest);
        sizesToTest.retainAll(
                Arrays.asList(CollectionSize.ZERO, CollectionSize.ONE, CollectionSize.SEVERAL));

        logger.fine("   Sizes: " + formatFeatureSet(sizesToTest));

        if (sizesToTest.isEmpty())
        {
            throw new IllegalStateException(
                    name
                            + ": no CollectionSizes specified (check the argument to "
                            + "FeatureSpecificTestSuiteBuilder.withFeatures().)");
        }

        TestSuite suite = new TestSuite(name);
        for (Feature<?> collectionSize : sizesToTest)
        {
            String oneSizeName =
                    Platform.format(
                            "%s [collection size: %s]", name, collectionSize.toString().toLowerCase());
            OneSizeGenerator<T, E> oneSizeGenerator =
                    new OneSizeGenerator<>(getSubjectGenerator(), (CollectionSize) collectionSize);
            Set<Feature<?>> oneSizeFeatures = Helpers.copyToSet(features);
            oneSizeFeatures.add(collectionSize);
            Set<Method> oneSizeSuppressedTests = getSuppressedTests();

            OneSizeTestSuiteBuilder<T, E> oneSizeBuilder =
                    new OneSizeTestSuiteBuilder<T, E>(testers)
                            .named(oneSizeName)
                            .usingGenerator(oneSizeGenerator)
                            .withFeatures(oneSizeFeatures)
                            .withSetUp(getSetUp())
                            .withTearDown(getTearDown())
                            .suppressing(oneSizeSuppressedTests);
            TestSuite oneSizeSuite = oneSizeBuilder.createTestSuite();
            suite.addTest(oneSizeSuite);

            for (TestSuite derivedSuite : createDerivedSuites(oneSizeBuilder))
            {
                oneSizeSuite.addTest(derivedSuite);
            }
        }
        return suite;
    }

    protected List<TestSuite> createDerivedSuites(
            FeatureSpecificTestSuiteBuilder<?, ? extends OneSizeTestContainerGenerator<T, E>>
                    parentBuilder)
    {
        return new ArrayList<>();
    }

    /**
     * Builds a test suite for one particular {@link CollectionSize}.
     */
    private static final class OneSizeTestSuiteBuilder<T, E>
            extends FeatureSpecificTestSuiteBuilder<
            OneSizeTestSuiteBuilder<T, E>, OneSizeGenerator<T, E>>
    {
        private final List<Class<? extends AbstractTester>> testers;

        public OneSizeTestSuiteBuilder(List<Class<? extends AbstractTester>> testers)
        {
            this.testers = testers;
        }

        @Override
        protected List<Class<? extends AbstractTester>> getTesters()
        {
            return testers;
        }
    }
}
